package com.autumn.scheduler.domain.services.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerKey;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.autumn.common.api.application.dto.input.PageQueryInput;
import com.autumn.common.api.application.dto.output.IPageQueryResult;
import com.autumn.mybatis.criterion.Criteria;
import com.autumn.mybatis.criterion.LockModeEnum;
import com.autumn.mybatis.criterion.Query;
import com.autumn.mybatis.criterion.SpecifyUpdate;
import com.autumn.scheduler.api.QueueSchedulerStatus;
import com.autumn.scheduler.api.constant.ApiConstants;
import com.autumn.scheduler.api.constant.ApiConstants.QueueRunState;
import com.autumn.scheduler.api.services.dto.input.AbstractAutumnJobDetailsInput;
import com.autumn.scheduler.api.services.dto.input.AutumnJobDetailsAddInput;
import com.autumn.scheduler.api.services.dto.input.AutumnJobDetailsUpdateInput;
import com.autumn.scheduler.api.services.dto.output.AutumnJobDetailsOutput;
import com.autumn.scheduler.api.services.dto.output.AutumnJobLogOutput;
import com.autumn.scheduler.domain.entities.AutumnJobDetails;
import com.autumn.scheduler.domain.entities.AutumnJobDetailsQuery;
import com.autumn.scheduler.domain.entities.AutumnJobLog;
import com.autumn.scheduler.domain.services.IJobService;
import com.autumn.scheduler.domain.values.SchedulerValue;
import com.autumn.scheduler.job.JobConstants;
import com.autumn.scheduler.job.trigger.AbstractJobTrigger;
import com.autumn.util.AutoMapUtils;
import com.autumn.util.DataRollbackException;
import com.autumn.util.ExceptionUtils;
import com.autumn.util.StringUtils;
import com.autumn.util.data.PageQueryBuilder;
import com.autumn.util.function.FunctionOneResult;
import com.autumn.util.json.JsonUtils;

/**
 * 
 * 描述：
 * 
 * @author 杨昌国 2018-03-20 00:29:21
 */
@Service
public class JobServiceImpl extends AbstractAutumnSchedulerService implements IJobService {

	private String getStringValue(String value) {
		if (value == null) {
			return "";
		}
		return value;
	}

	/**
	 * 设置Job Map 数据
	 * 
	 * @param jobDetail
	 * @param autumnJobDetail
	 */
	private void setJobDataMap(JobDetail jobDetail, AutumnJobDetails autumnJobDetail) {
		JobDataMap jobMap = jobDetail.getJobDataMap();
		jobMap.put(JobConstants.JobDataKey.JOB_FRIENDLY_NAME, autumnJobDetail.getJobFriendlyName());
		jobMap.put(JobConstants.JobDataKey.JOB_QUEUE_NAME, autumnJobDetail.getJobQueueName());
		jobMap.put(JobConstants.JobDataKey.EXECUTE_NUMBERS, autumnJobDetail.getExecuteNumbers());
		jobMap.put(JobConstants.JobDataKey.EXECUTE_OPERATION, autumnJobDetail.getExecuteOperation());
		jobMap.put(JobConstants.JobDataKey.EXECUTE_PARAMETER_MAP, autumnJobDetail.getExecuteParameter());
		jobMap.put(JobConstants.JobDataKey.RUN_NUMBERS, autumnJobDetail.getRunNumbers());
	}

	private JobDetail createQuartzJobDetail(AutumnJobDetails autumnJobDetail, SchedulerValue schedulerValue) {
		autumnJobDetail.setJobName(UUID.randomUUID().toString().replace("-", "").toUpperCase());
		autumnJobDetail.setSchedName(this.getSchedulerName());
		autumnJobDetail.setRunNumbers(0L);
		autumnJobDetail.setRunSuccessNumbers(0L);
		autumnJobDetail.setRunFailNumbers(0L);
		autumnJobDetail.setGmtCreate(new Date());
		autumnJobDetail.setGmtModified(null);
		autumnJobDetail.setExecuteParameter(getStringValue(autumnJobDetail.getExecuteParameter()));

		JobDetail jobDetail = JobBuilder.newJob(com.autumn.scheduler.job.RabbitJob.class)
				.withIdentity(autumnJobDetail.getJobName(), autumnJobDetail.getJobGroup())
				.withDescription(schedulerValue.getJobDescription()).build();

		this.setJobDataMap(jobDetail, autumnJobDetail);

		return jobDetail;
	}

	@Transactional(rollbackFor = DataRollbackException.class)
	@Override
	public void addJob(AutumnJobDetails autumnJobDetail, SchedulerValue schedulerValue, AbstractJobTrigger jobTrigger) {
		ExceptionUtils.checkNotNull(autumnJobDetail, "autumnJobDetail");
		ExceptionUtils.checkNotNull(jobTrigger, "jobTrigger");
		if (schedulerValue == null) {
			schedulerValue = new SchedulerValue();
		}
		Query query = new Query(AutumnJobDetails.class);
		query.eq("schedName", this.getSchedulerName()).eq("jobFriendlyName", autumnJobDetail.getJobFriendlyName())
				.eq("jobGroup", autumnJobDetail.getJobGroup()).lock(LockModeEnum.UPDATE);

		if (jobDetailsRepository.countByWhere(query.builderSection()) > 0) {
			throw ExceptionUtils.throwValidationException(String.format("同相的任务组(%s) ,已存在同一任务 (%s)。",
					autumnJobDetail.getJobGroup(), autumnJobDetail.getJobFriendlyName()));
		}

		JobDetail jobDetail = createQuartzJobDetail(autumnJobDetail, schedulerValue);
		Trigger trigger = jobTrigger.createTrigger(jobDetail, autumnJobDetail, schedulerValue);
		jobDetailsRepository.insert(autumnJobDetail);
		try {
			scheduler.scheduleJob(jobDetail, trigger);
			// scheduler.start();
		} catch (SchedulerException e) {
			throw ExceptionUtils.throwApplicationException(e.getMessage(), e);
		}
	}

	@Transactional(rollbackFor = DataRollbackException.class)
	@Override
	public void updateJob(AutumnJobDetails autumnJobDetail, SchedulerValue schedulerValue,
			AbstractJobTrigger jobTrigger) {
		ExceptionUtils.checkNotNull(autumnJobDetail, "autumnJobDetail");
		ExceptionUtils.checkNotNull(jobTrigger, "jobTrigger");
		if (schedulerValue == null) {
			schedulerValue = new SchedulerValue();
		}
		try {
			autumnJobDetail.setGmtModified(new Date());
			JobKey jobKey = JobKey.jobKey(autumnJobDetail.getJobName(), autumnJobDetail.getJobGroup());
			JobDetail jobDetail = JobBuilder.newJob(com.autumn.scheduler.job.RabbitJob.class)
					.withIdentity(autumnJobDetail.getJobName(), autumnJobDetail.getJobGroup())
					.withDescription(schedulerValue.getJobDescription()).build();

			TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup());
			TriggerState state = this.scheduler.getTriggerState(triggerKey);
			boolean isStart = !state.equals(TriggerState.PAUSED);
			this.scheduler.pauseTrigger(triggerKey);
			this.scheduler.pauseJob(jobKey);
			this.setJobDataMap(jobDetail, autumnJobDetail);
			this.scheduler.addJob(jobDetail, true, true);
			Trigger trigger = jobTrigger.createTrigger(jobDetail, autumnJobDetail, schedulerValue);
			jobDetailsRepository.update(autumnJobDetail);
			this.scheduler.rescheduleJob(triggerKey, trigger);
			if (isStart) {
				this.scheduler.resumeTrigger(triggerKey);
				this.scheduler.resumeJob(jobKey);
			} else {
				this.scheduler.pauseTrigger(triggerKey);
				this.scheduler.pauseJob(jobKey);
			}
		} catch (SchedulerException e) {
			throw ExceptionUtils.throwApplicationException(e.getMessage(), e);
		}
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public void deleteJob(JobKey key) {
		ExceptionUtils.checkNotNull(key, "key");
		this.deleteJob(key, delKey -> {
			Criteria criteria = new Criteria(AutumnJobDetails.class);
			criteria.eq("schedName", this.getSchedulerName()).eq("jobName", delKey.getName()).eq("jobGroup",
					delKey.getGroup());
			jobDetailsRepository.deleteByWhere(criteria.builderSection());
		}, 1000);
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public boolean deleteJob(long id) {
		AutumnJobDetails details = jobDetailsRepository.getByLock(id, LockModeEnum.UPDATE);
		if (details != null) {
			JobKey key = JobKey.jobKey(details.getJobName(), details.getJobGroup());
			deleteJob(key, delKey -> {
				jobDetailsRepository.deleteByPrimaryKey(id);
			}, 1000);
			return true;
		}
		return false;
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public void runJob(AutumnJobLog log, boolean isDeleteJob) {
		logRepository.insert(log);
		boolean isOk = log.getRunState() == ApiConstants.RunState.SUCCESS;
		if (isDeleteJob && isOk) {
			JobKey key = JobKey.jobKey(log.getJobName(), log.getJobGroup());
			this.deleteJob(key);
		} else {
			Query query = new Query(AutumnJobDetails.class);
			query.eq("schedName", this.getSchedulerName()).eq("jobName", log.getJobName())
					.eq("jobGroup", log.getJobGroup()).lock(LockModeEnum.UPDATE);
			AutumnJobDetails details = jobDetailsRepository.selectFirst(query.builderSection());
			if (details != null) {
				long runNumbers = details.getRunNumbers() + 1L;
				SpecifyUpdate spe = new SpecifyUpdate(AutumnJobDetails.class);
				spe.eq("id", details.getId()).set("runNumbers", runNumbers);
				if (log.getRunState() == ApiConstants.RunState.SUCCESS) {
					spe.set("runSuccessNumbers", (details.getRunSuccessNumbers() + 1L));
				} else {
					spe.set("runFailNumbers", (details.getRunFailNumbers() + 1L));
				}
				jobDetailsRepository.updateBySpecify(spe.builderSection());
			}
		}
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public int updateJobStatus(QueueSchedulerStatus status) {
		ExceptionUtils.checkNotNull(status, "status");
		if (StringUtils.isNullOrBlank(status.getJobExecuteId())) {
			ExceptionUtils.throwValidationException("作业执行id为null或空白。");
		}
		if (status.getState() == null) {
			ExceptionUtils.throwValidationException("作业执行的 state 为 null。");
		}
		if (status.getBeginTime() == null) {
			ExceptionUtils.throwValidationException("作业执行的 beginTime 为 null。");
		}
		if (status.getEndTime() == null) {
			ExceptionUtils.throwValidationException("作业执行的 endTime 为 null。");
		}
		String msg = QueueRunState.getMessage(status.getState());
		if (StringUtils.isNullOrBlank(msg)) {
			ExceptionUtils.throwValidationException("无效的作业执行 state。");
		}
		String message = status.getMessage();
		if (StringUtils.isNullOrBlank(message)) {
			message = msg;
		}
		SpecifyUpdate sp = new SpecifyUpdate(AutumnJobLog.class);
		sp.eq("jobExecuteId", status.getJobExecuteId().trim()).set("jobQueueRunState", status.getState())
				.set("jobQueueRunMessage", message).set("jobQueueBeginTime", new Date(status.getBeginTime()))
				.set("jobQueueEndTime", new Date(status.getEndTime()));
		return logRepository.updateBySpecify(sp.builderSection());
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public void pauseJob(JobKey key) {
		ExceptionUtils.checkNotNull(key, "key");
		try {
			this.scheduler.pauseJob(key);
		} catch (SchedulerException e) {
			throw ExceptionUtils.throwApplicationException(e.getMessage(), e);
		}
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public boolean pauseJob(long id) {
		JobKey key = this.getJobKey(id);
		if (key != null) {
			this.pauseJob(key);
			return true;
		}
		return false;
	}

	/**
	 * 查询全部任务的页大小
	 */
	private static final int QUEAY_ALL_PAGE_SIZE = 2000;

	/**
	 * 全部执行任务
	 * 
	 * @param act
	 * @return
	 *
	 */
	private int allExecuteJob(FunctionOneResult<JobKey, Integer> act) {
		Query query = new Query(AutumnJobDetails.class);
		query.orderBy("id");
		int pageIndex = 1;
		int count = 0;
		List<AutumnJobDetails> items = new ArrayList<>();
		do {
			query.page(pageIndex, QUEAY_ALL_PAGE_SIZE);
			items = jobDetailsRepository.selectList(query.builderSection());
			for (AutumnJobDetails item : items) {
				JobKey key = this.getJobKey(item);
				try {
					count += act.apply(key);
				} catch (Exception e) {

				}
			}
			pageIndex++;
		} while (items.size() >= QUEAY_ALL_PAGE_SIZE);
		return count;
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public int pauseAllJob() {
		return allExecuteJob(key -> {
			try {
				this.scheduler.pauseJob(key);
				return 1;
			} catch (SchedulerException e) {
				return 0;
			}
		});
	}

	/**
	 * 触发任务
	 * 
	 * @param key
	 *            键
	 *
	 */
	@Override
	public void triggerJob(JobKey key) {
		ExceptionUtils.checkNotNull(key, "key");
		try {
			this.scheduler.triggerJob(key);
		} catch (SchedulerException e) {
			throw ExceptionUtils.throwApplicationException(e.getMessage(), e);
		}
	}

	/**
	 * 触发任务
	 * 
	 * @param id
	 *            主键
	 *
	 */
	@Override
	public void triggerJob(long id) {
		JobKey key = this.getJobKey(id);
		if (key != null) {
			this.triggerJob(key);
		}
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public void resumeJob(JobKey key) {
		ExceptionUtils.checkNotNull(key, "key");
		try {
			this.scheduler.resumeJob(key);
		} catch (SchedulerException e) {
			throw ExceptionUtils.throwApplicationException(e.getMessage(), e);
		}
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public boolean resumeJob(long id) {
		JobKey key = this.getJobKey(id);
		if (key != null) {
			this.resumeJob(key);
			return true;
		}
		return false;
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public int resumeAllJob() {
		return allExecuteJob(key -> {
			try {
				this.scheduler.resumeJob(key);
				return 1;
			} catch (SchedulerException e) {
				return 0;
			}
		});
	}

	@Override
	public JobKey getJobKeyByFriendly(String jobFriendlyName, String jobGroup) {
		Query query = new Query(AutumnJobDetails.class);
		query.eq("schedName", this.getSchedulerName()).eq("jobFriendlyName", jobFriendlyName).eq("jobGroup", jobGroup);
		AutumnJobDetails jobDetails = jobDetailsRepository.selectFirst(query.builderSection());
		if (jobDetails == null) {
			return null;
		}
		return JobKey.jobKey(jobDetails.getJobName(), jobDetails.getJobGroup());
	}

	@Override
	@Transactional(rollbackFor = DataRollbackException.class)
	public AutumnJobDetails getJobDetailsByFriendly(String jobFriendlyName, String jobGroup) {
		Query query = new Query(AutumnJobDetails.class);
		query.eq("schedName", this.getSchedulerName()).eq("jobFriendlyName", jobFriendlyName).eq("jobGroup", jobGroup)
				.lock(LockModeEnum.UPDATE);
		AutumnJobDetails jobDetails = jobDetailsRepository.selectFirst(query.builderSection());
		return jobDetails;
	}

	/**
	 * 获取作业
	 * 
	 * @param id
	 *            主键
	 */
	@Override
	public AutumnJobDetailsOutput getJob(long id) {
		AutumnJobDetailsQuery result = jobQueryDetailsRepository.get(id);
		AutumnJobDetailsOutput output = AutoMapUtils.map(result, AutumnJobDetailsOutput.class);
		if (result != null && output != null) {
			this.setJobOutput(result, output);
		}
		return output;
	}

	private void setJobOutput(AutumnJobDetailsQuery source, AutumnJobDetailsOutput output) {
		if (source.getStartTime() != null) {
			output.setFirstStartTime(new Date(source.getStartTime()));
		} else {
			output.setFirstStartTime(null);
		}
		if (source.getPrevFireTime() != null && source.getPrevFireTime() > 1L) {
			output.setPreviousExecuteTime(new Date(source.getPrevFireTime()));
		} else {
			output.setPreviousExecuteTime(null);
		}
		if (source.getNextFireTime() != null) {
			output.setNextExecuteTime(new Date(source.getNextFireTime()));
		} else {
			output.setNextExecuteTime(null);
		}
	}

	private class JobDetailsInputInfo {
		private final AutumnJobDetails jobDetails;
		private final SchedulerValue schedulerValue;
		private final AbstractJobTrigger jobTrigger;

		public JobDetailsInputInfo(AutumnJobDetails jobDetails, SchedulerValue schedulerValue,
				AbstractJobTrigger jobTrigger) {
			super();
			this.jobDetails = jobDetails;
			this.schedulerValue = schedulerValue;
			this.jobTrigger = jobTrigger;
		}

		public AutumnJobDetails getJobDetails() {
			return jobDetails;
		}

		public SchedulerValue getSchedulerValue() {
			return schedulerValue;
		}

		public AbstractJobTrigger getJobTrigger() {
			return jobTrigger;
		}

	}

	private JobDetailsInputInfo createAutumnJobDetails(AbstractAutumnJobDetailsInput input) {
		if (input.getExecuteOperation() == null) {
			input.setExecuteOperation(ApiConstants.ExecuteOperation.NONE);
		}
		if (input.getExecuteNumbers() > 0) {
			String value = ApiConstants.ExecuteOperation.getMessage(input.getExecuteOperation());
			if (StringUtils.isNullOrBlank(value)) {
				ExceptionUtils.throwValidationException("执行作业次数时，指定执行后操作无效。");
			}
		} else {
			input.setExecuteOperation(ApiConstants.ExecuteOperation.NONE);
		}
		AbstractJobTrigger jobTrigger = AbstractJobTrigger.createJobTrigger(input);
		if (!StringUtils.isNullOrBlank(input.getExecuteParameter())) {
			try {
				Map<String, Object> value = JsonUtils.parseMap(input.getExecuteParameter().trim());
				input.setExecuteParameter(JsonUtils.toJSONString(value));
			} catch (Exception e) {
				ExceptionUtils.throwValidationException("执行参数不为空时，必须是Json对象。");
			}
		}
		if (input.getFirstStartTime() == null) {
			input.setFirstStartTime(new Date());
		}
		AutumnJobDetails jobDetails = AutoMapUtils.map(input, AutumnJobDetails.class);
		SchedulerValue schedulerValue = new SchedulerValue();
		schedulerValue.setJobDescription(input.getJobDescription());
		schedulerValue.setTriggerDescription(input.getTriggerDescription());
		schedulerValue.setFirstStartTime(input.getFirstStartTime());
		return new JobDetailsInputInfo(jobDetails, schedulerValue, jobTrigger);
	}

	@Override
	public AutumnJobDetailsOutput addJob(AutumnJobDetailsAddInput input) {
		JobDetailsInputInfo info = createAutumnJobDetails(input);
		this.addJob(info.getJobDetails(), info.getSchedulerValue(), info.getJobTrigger());
		return this.getJob(info.getJobDetails().getId());
	}

	@Override
	public AutumnJobDetailsOutput updateJob(AutumnJobDetailsUpdateInput input) {
		AutumnJobDetails jobDetails = jobDetailsRepository.get(input.getId());
		if (jobDetails == null) {
			ExceptionUtils.throwUserFriendlyException("无法更新不存在的数据。");
		}
		JobDetailsInputInfo info = createAutumnJobDetails(input);
		String oldName = jobDetails.getJobFriendlyName();
		if (!oldName.equalsIgnoreCase(input.getJobFriendlyName())) {
			Query query = new Query(AutumnJobDetails.class);
			query.eq("schedName", this.getSchedulerName()).eq("jobFriendlyName", input.getJobFriendlyName())
					.eq("jobGroup", jobDetails.getJobGroup()).lock(LockModeEnum.UPDATE);
			if (jobDetailsRepository.countByWhere(query.builderSection()) > 0) {
				throw ExceptionUtils.throwValidationException(String.format("同相的任务组(%s) ,已存在同一任务 (%s)。",
						jobDetails.getJobGroup(), input.getJobFriendlyName()));
			}
		}
		AutoMapUtils.map(input, jobDetails);
		this.updateJob(jobDetails, info.getSchedulerValue(), info.getJobTrigger());
		return this.getJob(jobDetails.getId());
	}

	private final static int PAGE_MAX_VALUE = 100;

	@Override
	public IPageQueryResult<AutumnJobDetailsOutput> jobQuery(PageQueryInput input) {
		PageQueryBuilder<AutumnJobDetailsQuery> builder = new PageQueryBuilder<>(AutumnJobDetailsQuery.class);
		return builder.page(input, PAGE_MAX_VALUE).apply((q) -> {
			q.orderBy("id");
		}).toPageResult(jobQueryDetailsRepository, AutumnJobDetailsOutput.class, (s, t) -> {
			this.setJobOutput(s, t);
		});
	}

	@Override
	public IPageQueryResult<AutumnJobLogOutput> logQuery(PageQueryInput input) {
		PageQueryBuilder<AutumnJobLog> builder = new PageQueryBuilder<>(AutumnJobLog.class);
		return builder.page(input, PAGE_MAX_VALUE).apply((q) -> {
			q.orderByDescending("id");
		}).toPageResult(logRepository, AutumnJobLogOutput.class);
	}

	@Override
	public void clearAllJobLog() {
		logRepository.truncateTable();
	}
}
